#include <buola/gui.h>

#include <buola/gui/x11/cx11drag.h>
#include <buola/gui/dnd/cdrag.h>
#include <buola/gui/dnd/cdndwindow.h>
#include <buola/gui/x11/cx11dndfilter.h>

#include <X11/Xatom.h>
#include <buola/gui/x11/cx11toolkit.h>
#include <buola/gui/x11/cx11clipboard.h>
#include <buola/gui/x11/cx11display.h>
#include <buola/gui/x11/cx11window.h>

namespace buola { namespace gui {

using namespace x11;
    
CX11DndFilter *CX11Drag::mX11DndFilter=nullptr;
    
CX11Drag::CX11Drag(CDrag *pDrag)
{
    mDrag=pDrag;
    mDragLastAware=0;
    mDragLastAccepted=false;
    mDragLastRejected=false;
    mDragWaitingStatus=false;
    mDragPositionChanged=false;
    mDragWaitingToDrop=false;

    mWindowCache=nullptr;
}

CX11Drag::~CX11Drag()
{
    if(mWindowCache)
        delete[] mWindowCache;
}

bool CX11Drag::OnX11Event(XEvent *pE)
{
    if(pE->type==ButtonRelease)
    {
        DragFinish();
        return false;
    }
    else if(pE->type==MotionNotify)
    {
        DragMove(CPoint_d(pE->xmotion.x_root,pE->xmotion.y_root));
        return false;
    }
    
    return true;
}

Window CX11Drag::FindWindowIDAt(const CPoint_d&,int* /*pDndAware*/)
{
    ///\todo
//    Window lDecorationID=mDrag->mDecoration->GetID();
//    *pDndAware=0;
//    
//    Window lCurrent=get_x11_toolkit()->GetRootWindow()->GetID();
//    Window lPrevious=lCurrent;
//    int lX=(int)pP.x;
//    int lY=(int)pP.y;
//    
//    Window lChild;
//    int lRX;
//    int lRY;
//
//    for(;;)
//    {
//        //Check if window is DndAware
//        void *lPropReturn;
//            
//        if(get_default_x11_display()->GetWindowProperty(lCurrent,get_default_x11_display()->Atoms().mDndAware,
//                            XA_ATOM,&lPropReturn)>0)
//        {
//            int *lProp=(int*)lPropReturn;
//            *pDndAware=*lProp;
//        }
//
//        //Find adequate child
//        XTranslateCoordinates(get_default_x11_display()->GetDisplay(),lPrevious,lCurrent,
//                        lX,lY,&lRX,&lRY,&lChild);
//
//        //If the window is the decoration...
//        if(lChild==lDecorationID)
//        {
//            if(!mWindowCache)  //Create the window cache
//            {
//                Window lDummy1,lDummy2,*lList;
//                unsigned int lNum;
//
//                XQueryTree(get_default_x11_display()->GetDisplay(),lCurrent,
//                                    &lDummy1,&lDummy2,&lList,&lNum);
//                if(lNum)
//                {
//                    mWindowCacheCount=lNum;
//                    mWindowCache=new CX11DragWindowInfo[lNum];
//                    
//                    for(unsigned int i=0;i<lNum;i++)
//                    {
//                        //reverse order
//                        mWindowCache[i].mWin=lList[lNum-1-i];
//                        mWindowCache[i].mChecked=false;
//                    }
//                }
//                XFree(lList);
//            }
//            
//            lChild=None;
//            
//            for(int i=0;i<mWindowCacheCount;i++)
//            {
//                if(!mWindowCache[i].mChecked)
//                {
//                    mWindowCache[i].mChecked=true;
//                    
//                    if(mWindowCache[i].mWin==lDecorationID)
//                    {
//                        mWindowCache[i].mOK=false;
//                    }
//                    else
//                    {
//                        XWindowAttributes lAttr;
//                        XGetWindowAttributes(get_default_x11_display()->GetDisplay(),
//                                            mWindowCache[i].mWin,&lAttr);
//
//                        if(lAttr.map_state==IsUnmapped)
//                        {
//                            mWindowCache[i].mOK=false;
//                        }
//                        else
//                        {
//                            mWindowCache[i].mOK=true;
//                            mWindowCache[i].mRect.Set(CPoint_d(lAttr.x,lAttr.y),
//                                            CSize_d(lAttr.width,lAttr.height));
//                        }
//                    }
//                }
//                
//                if(!mWindowCache[i].mOK) continue;
//                if(!mWindowCache[i].mRect.Contains(CPoint_d(lRX,lRY))) continue;
//
//                lChild=mWindowCache[i].mWin;
//                break;
//            }
//        }
//
//        if(lChild)
//        {
//            lX=lRX;
//            lY=lRY;
//            lPrevious=lCurrent;
//            lCurrent=lChild;
//        }
//        else
//            break;
//    }   
//
//    return lCurrent;
    return None;
}

//////////
// drag 
//

void CX11Drag::DragStart()
{
    //TODO:Set mCurrentDrag->mSourceWin->GetID() as window instead of 
    //default proxy
    if(!mX11DndFilter)
        mX11DndFilter=new CX11DndFilter;
    mX11DndFilter->SetCurrentDrag(mDrag);

    get_x11_toolkit()->GetDispatcher()->AddFilter(this);

    DragMove(get_x11_toolkit()->GetMousePos());
}

void CX11Drag::DragMove(const CPoint_d &pP)
{
    mDrag->Move(pP);
    
    int lDndAware;
    Window lID=FindWindowIDAt(pP,&lDndAware);

    if(lID==mDragLastOver) //we are in the same window
    {
        if(mDragLastAware)
        {
            mDragLastPosition=pP;

            if(mDragWaitingStatus)
            {
                mDragPositionChanged=true;
            }
            else
            {
                DragSendPosition(mDragLastOver,pP);
            }
        }
    }
    else    //we are in a different window
    {
        mDragLastRectangle={-1,-1,-1,-1};
    
        if(mDragLastOver&&mDragLastAware)
            DragSendLeave(mDragLastOver);

        mDragLastOver=lID;

        if(lDndAware<3)
        {
            mDragLastAware=lDndAware;
            mDragLastAccepted=false;
            mDragLastRejected=true;

            mDrag->SetStatus(DND_NOTAWARE);
        }
        else
        {
            mDragLastAware=lDndAware;
            mDragLastAccepted=false;
            mDragLastRejected=false;
            
            DragSendEnter(mDragLastOver);
            DragSendPosition(mDragLastOver,pP);
            
            mDragLastPosition=pP;
            mDragWaitingStatus=true;
            
            mDrag->SetStatus(DND_UNKNOWN);
        }
    }
}

void CX11Drag::DragFinish()
{
    mDrag->Finish();
    mDrag->SetStatus(DND_FINISHED);

    get_x11_toolkit()->GetDispatcher()->RemoveFilter(this);

    if(!mDragWaitingStatus)
        DragDrop();
    else
        mDragWaitingToDrop=true;
}

void CX11Drag::DragDrop()
{
    if(mDragLastAccepted)
    {
        DragSendDrop(mDragLastOver);
        mDrag->Drop();
    }
    else    
    {
        DragSendLeave(mDragLastOver);
        mDrag->Discard();
        get_x11_toolkit()->GetClipboard(CClipboard::DRAGNDROP)->Clear();
    }
}

void CX11Drag::DragSendPosition(Window,const CPoint_d&)
{
    ///\todo
//    if(mDragLastRectangle.Contains(pP)) return;
//    
//    Window lS=mDrag->mSourceWin->GetID();
//    get_default_x11_display()->SendClientMessage(pW,pW,get_default_x11_display()->Atoms().mDndPosition,
//            lS,0,(((unsigned int)pP.x)<<16)|((unsigned int)pP.y),
//                                   get_x11_toolkit()->mLastTimeStamp,get_default_x11_display()->Atoms().mDndActionCopy);
//
//    mDragWaitingStatus=true;
//    mDragPositionChanged=false;
}

void CX11Drag::DragSendEnter(Window)
{
    ///\todo
//    Window lS=mDrag->mSourceWin->GetID();
//    
//    unsigned int lFlags=((unsigned int)mDragLastAware)<<24;
//    Atom lA1=0,lA2=0,lA3=0;
//    
//    CX11Clipboard *lCB=(CX11Clipboard*)get_x11_toolkit()->
//                            GetClipboard(CClipboard::DRAGNDROP);
//    int lCount=lCB->mAvailableTargets.size();
//    if(!lCount)
//    {
//        fatal_error("No target available in DndEnter");
//    }
//    
//    lA1=lCB->mAvailableTargets[0];
//    
//    if(lCount>1)
//    {
//        lA2=lCB->mAvailableTargets[1];
//
//        if(lCount>2)
//        {
//            lA3=lCB->mAvailableTargets[2];
//            
//            if(lCount>3)
//            {
//                lFlags|=1;
//                
//                get_default_x11_display()->SetWindowProperty(lS,get_default_x11_display()->Atoms().mDndTypeList,
//                                    lCB->mAvailableTargets);
//            }
//        }
//    }
//
//    get_default_x11_display()->SendClientMessage(pW,pW,get_default_x11_display()->Atoms().mDndEnter,
//                                lS,lFlags,lA1,lA2,lA3);
}

void CX11Drag::DragSendLeave(Window)
{
    ///\todo
//    Window lS=mDrag->mSourceWin->GetID();
//    get_default_x11_display()->SendClientMessage(pW,pW,get_default_x11_display()->Atoms().mDndLeave,lS);
}

void CX11Drag::DragSendDrop(Window)
{
    ///\todo
//    Window lS=mDrag->mSourceWin->GetID();
//    get_default_x11_display()->SendClientMessage(pW,pW,get_default_x11_display()->Atoms().mDndDrop,
//                                   lS,0,get_x11_toolkit()->mLastTimeStamp);
//
//    mDragWaitingStatus=true;
//    mDragPositionChanged=false;
}

void CX11Drag::DragSendFinished(Window)
{
    ///\todo
//    Window lS=mDrag->mSourceWin->GetID();
//    get_default_x11_display()->SendClientMessage(pW,pW,get_default_x11_display()->Atoms().mDndPosition,lS);
}

void CX11Drag::ProcessDndStatus(XClientMessageEvent *pE)
{
    mDragWaitingStatus=false;

    if((Window)pE->data.l[0]!=mDragLastOver)
    {
        msg_warn() << "Wrong DndStatus message " << pE->data.l[0] << " " <<
                    pE->data.l[1] << " " << mDragLastRectangle << "\n";
        return;
    }
        
    if(pE->data.l[1]&0x0001)
    {
        mDrag->SetStatus(DND_ACCEPTED);
        mDragLastAccepted=true;
    }
    else
    {
        mDrag->SetStatus(DND_REJECTED);
        mDragLastRejected=true;
    }
        
    unsigned long lPos=pE->data.l[2];
    unsigned long lSize=pE->data.l[3];
    mDragLastRectangle={CPoint_d(lPos>>16,lPos&0xffff),
                           CSize_d((lSize>>16)+1,(lSize&0xffff)+1)};

    if(mDragPositionChanged)
    {
        if(mDragWaitingToDrop)
            DragDrop();
        else
            DragSendPosition(mDragLastOver,mDragLastPosition);
    }
}

/*namespace gui*/ } /*namespace buola*/ }
